/*
 * Copyright 2011-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package xyz.xiezc.mzix.schedule.config;

import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.LFUCache;
import cn.hutool.core.bean.BeanUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Setter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.lang.Nullable;

import java.util.Map;

/**
 * {@link RedisSerializer} that can read and write JSON using
 * <a href="https://github.com/FasterXML/jackson-core">Jackson's</a> and
 * <a href="https://github.com/FasterXML/jackson-databind">Jackson Databind</a> {@link ObjectMapper}.
 * <p>
 * This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances.
 * <b>Note:</b>Null objects are serialized as empty arrays and vice versa.
 *
 * @author Thomas Darimont
 * @since 1.2
 */
public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {


    LFUCache<String, Class> classCache = CacheUtil.newLFUCache(50);


    byte[] EMPTY_ARRAY = new byte[0];

    @Setter
    private ObjectMapper objectMapper = new ObjectMapper();


    @SuppressWarnings("unchecked")
    public T deserialize(@Nullable byte[] bytes) throws SerializationException {

        if (isEmpty(bytes)) {
            return null;
        }
        try {
            Map<String, Object> map = this.objectMapper.readValue(bytes, 0, bytes.length, Map.class);
            String javaType = (String) map.get("JavaType");
            Class aClass = classCache.get(javaType, () -> {
                return Class.forName(javaType);
            });
            return (T) BeanUtil.toBean(map, aClass);
        } catch (Exception ex) {
            throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
        }
    }

    @Override
    public byte[] serialize(@Nullable Object t) throws SerializationException {

        if (t == null) {
            return EMPTY_ARRAY;
        }
        try {
            Map<String, Object> stringObjectMap = BeanUtil.beanToMap(t);
            stringObjectMap.put("JavaType", t.getClass().getName());
            return this.objectMapper.writeValueAsBytes(stringObjectMap);
        } catch (Exception ex) {
            throw new SerializationException("Could not write JSON: " + ex.getMessage(), ex);
        }
    }

    static boolean isEmpty(@Nullable byte[] data) {
        return (data == null || data.length == 0);
    }

}
